package com.hfi.observer.demo04;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

/**
 * demo3：抽象的Notifier还是依赖抽象的Observer
 * 如果Notifier和Observer根本就不知道 由客户端决定通知谁（使用反射，将ConcreteObserver和类的方法传入，无Observer接口）
 * Created by Administrator on 2017/6/30.
 */
class StockObserver {
    private String name;
    private Notifier notifier;

    public StockObserver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    public void CloseStockMarket() {
        System.out.println(notifier.getAction() + " " + name + "关闭股票行情，继续工作 ");
    }

    public void OpenStockMarket() {
        System.out.println(notifier.getAction() + " " + name + "打开股票行情 ");
    }
}

class NBAObserver {
    private String name;
    private Notifier notifier;

    public NBAObserver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    public void CloseNBADirectSeeding() {
        System.out.println(notifier.getAction() + " " + name + "关闭NBA直播，继续工作 ");
    }

    public void OpenNBADirectSeeding() {
        System.out.println(notifier.getAction() + " " + name + "打开NBA直播 ");
    }
}

interface Notifier {
    void notifier();

    void setAction(String action);

    String getAction();
}


/*这里稍稍想想, 到底如何将类StockObserver和类的指定方法传入到另1个类呢?
        其实我们可以把它拆分成两部分:
        1. 传送对象本身(Object).
        2. 传送方法的名字(String).
        至于怎样把这两种不同类型的东西放入类S的容器?  方法有很多种,
        这里我新建1个类ObjMethod, 把这两种东西封装在一起.
        而且我是打算把它放入HashSet容器的, 所以重写了hashCode()和 equals()方法, 只要上面两个成员相等, 我们就认为是相同的两个对象.*/

class ObjMethod {
    private Object obj;
    private String method;

    public ObjMethod(Object obj, String method) {
        this.obj = obj;
        this.method = method;
    }

    public String getMethod() {
        return this.method;
    }

    public Object getObj() {
        return this.obj;
    }

    // 自己定义相等的逻辑
    @Override
    public boolean equals(Object o) {
        ObjMethod m = (ObjMethod) o;
        //对象之间== 比较两个对象引用中存储的对象地址是不是一样的

        //两个对象引用的地址相同 并且 调用的方法名也相同 那么就是相同的Object，不再插入HashSet中
        return (this.getObj() == m.getObj()) && (this.getMethod().equals(m.getMethod()));
    }

    // 对HashSet而言，存入对象的流程为：
    // 根据对象的hash码，经过hash算法，找到对象应该存放的位置，如果该位置为空，则将对象存入该位置；
    // 如果该位置不为空，则使用equals()比较该位置的对象和将要入的对象，
    // 如果两个相等，则不再插入，如果不相等，根据hash冲突解决算法将对象插入其他位置。
    @Override
    public int hashCode() {
        return this.getObj().hashCode() * this.getMethod().hashCode();
    }
}

/**
 * 抽象通知者 由于不希望依赖 抽象观察者
 */
class Boss implements Notifier {

    // 需求：在StockObserver及NBAObserver不被修改的前提下（即不像demo03那样可以实现Observer）
    // 在Notifier中可以添加若干个StockObserver或NBAObserver的对象
    // 并通知他们执行自己的方法

    private HashSet<ObjMethod> methodList = new HashSet<ObjMethod>();
    private String action;

    public void attach(Object obj, String method) {
        this.methodList.add(new ObjMethod(obj, method));
    }

    public void detach(Object obj, String method) {
        this.methodList.remove(new ObjMethod(obj, method));
    }

    public void detachAll() {
        methodList.clear();
    }

    @Override
    public void notifier() {
        if (this.methodList.isEmpty()) {
            return;
        }
        //1. 从HashSet获取对象Obj 和 方法名method
        Iterator<ObjMethod> it = this.methodList.iterator();
        while (it.hasNext()) {
            ObjMethod m = it.next();
            // get the class of the object
            Class<?> objClass = m.getObj().getClass();
            try {
                // no any parameters
                Method method = objClass.getMethod(m.getMethod(), new Class[]{});
                // 在具有指定参数的指定对象上调用此Method对象表示的底层方法。
                // no parameters
                method.invoke(m.getObj(), new Object[]{});
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void setAction(String action) {
        this.action = action;
    }

    @Override
    public String getAction() {
        return action;
    }
}


public class Test {
    public static void main(String[] args) {
        Boss huhansan = new Boss();

        //关注胡汉三
        //我想关注多个通知者 如何解决呢？ 留坑-->实际上 一个观察者也可以观察多个通知者的，但这就不属于观察者模式了
        StockObserver tongshi1 = new StockObserver("魏关姹", huhansan);
        NBAObserver tongshi2 = new NBAObserver("易管查", huhansan);
        StockObserver tongshi3 = new StockObserver("魏关姹", huhansan);

        huhansan.setAction("老板离开了");
        //清除通知列表
        huhansan.detachAll();
        //注册通知列表
        huhansan.attach(tongshi1, "OpenStockMarket");
        huhansan.attach(tongshi2, "OpenNBADirectSeeding");
        huhansan.notifier();

        //模拟一段时间后
        try {
            TimeUnit.SECONDS.sleep(2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //发现老板回来
        huhansan.setAction("老板回来了！");
        //清除通知列表
        huhansan.detachAll();
        //胡汉三注册通知列表
        huhansan.attach(tongshi1, "CloseStockMarket");
        huhansan.attach(tongshi2, "CloseNBADirectSeeding");
        //重复注册（无效）
        huhansan.attach(tongshi2, "CloseNBADirectSeeding");
        //解除注册
        huhansan.detach(tongshi1, "CloseStockMarket");
        huhansan.notifier();
    }
}
